namespace NAct.Tests.Research
{
	using System;
	using System.Collections.Generic;
	using System.Reflection;
	using NUnit.Framework;
	using NUnit.Framework.ExtensionMethods;

	[ TestFixture ]
	public class DealingWithArrays
	{
		private const string INPUT_TEXT = "1,2,3,4,5,6,7,8,9,0";

		private readonly IDictionary<Type, Func<string, object>> parsers = new Dictionary<Type, Func<string, object>>
		                                                                   {
		                                                                   	{ typeof( int ), input => int.Parse( input ) }
		                                                                   };

		[ Test ]
		public void Should_be_able_to_figure_out_if_a_parameter_is_an_Array()
		{
			typeof( ArrayTestType ).GetMethod( "GetNumbers" ).GetParameters()[ 0 ].ParameterType.IsArray.Should( Be.True );

			DisplayInfoAbout( typeof( ArrayTestType ).GetMethod( "GetNumbers" ).GetParameters()[ 0 ] );
			DisplayInfoAbout( typeof( ArrayTestType ).GetMethod( "GetOneNumber" ).GetParameters()[ 0 ] );
			DisplayInfoAbout( typeof( ArrayTestType ).GetMethod( "GetAnObject" ).GetParameters()[ 0 ] );
		}

		private Array CreateArrayUsing( Type arrayType, string[] values )
		{
			Array arrayInstance = Array.CreateInstance( arrayType, values.Length );

			for ( int i = 0; i < values.Length; i++ )
			{
				arrayInstance.SetValue( parsers[ arrayType ]( values[ i ] ), i );
			}

			return arrayInstance;
		}

		[ Test ]
		public void Should_be_able_to_use_a_single_parser_to_build_an_array()
		{
			string[] values = INPUT_TEXT.Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries );

			MethodInfo method = typeof( ArrayTestType ).GetMethod( "GetNumbers" );

			method.GetParameters()[ 0 ].ParameterType.IsArray.Should( Be.True );
			Array arrayInstance = CreateArrayUsing( method.GetParameters()[ 0 ].ParameterType.GetElementType(), values );

			arrayInstance.Length.Should( Be.EqualTo( values.Length ) );
			arrayInstance.GetValue( 2 ).Should( Be.EqualTo( 3 ) );
			method.Invoke( InstanceOf<ArrayTestType>(), new[] { arrayInstance } );
		}

		private static object InstanceOf<T>()
		{
			return typeof( T ).GetConstructor( new Type[] { } ).Invoke( null );
		}

		private static void DisplayInfoAbout( ParameterInfo parameter )
		{
			Console.WriteLine( "Base Type: {0}", parameter.ParameterType.BaseType );
			Console.WriteLine( "Declaring Type: {0}", parameter.ParameterType.DeclaringType );
			Console.WriteLine( "Element Type: {0}", parameter.ParameterType.GetElementType() );
			Console.WriteLine( "Get Type: {0}", parameter.ParameterType.GetType() );
			Console.WriteLine( "Reflected Type: {0}", parameter.ParameterType.ReflectedType );
			Console.WriteLine( "Underlying System Type: {0}", parameter.ParameterType.UnderlyingSystemType );
			Console.WriteLine( "Is generic: {0}", parameter.ParameterType.IsGenericType );
			if ( parameter.ParameterType.IsGenericType )
			{
				foreach ( Type argument in parameter.ParameterType.GetGenericArguments() )
				{
					Console.WriteLine( "\tGeneric argument: {0}", argument );
				}
			}
			Console.WriteLine();
		}
	}

	internal class ArrayTestType
	{
		public void GetNumbers( int[] values )
		{
			foreach ( int i in values )
			{
				Console.WriteLine( "Value {0}", i );
			}
		}

		public void GetOneNumber( int value ) {}
		public void GetAnObject( object value ) {}
	}
}